package com.example.bbs.ui.view;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaPlayer;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.alibaba.fastjson.JSON;
import com.example.bbs.R;
import com.example.bbs.bean.MediaData;
import com.example.bbs.bean.Post;
import com.example.bbs.tool.Counter;
import com.example.bbs.ui.view.video.CoverVideoView;
import com.example.bbs.util.BaseUtil;
import com.example.bbs.util.DisplayUtil;
import com.example.bbs.util.FileUtil;
import com.example.bbs.util.ImageUtil;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import com.shuyu.gsyvideoplayer.GSYVideoManager;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

public class RichTextView extends RecyclerView {

    private abstract class ViewHolder extends RecyclerView.ViewHolder {

        ImageView ivItemBorder = null;
        Button btnItemDelete = null;
        Button btnItemOption = null;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);

            if (!(this instanceof TextViewHolder)) {
                ivItemBorder = itemView.findViewById(R.id.iv_item_border);
                btnItemDelete = itemView.findViewById(R.id.mb_item_delete);
                btnItemOption = itemView.findViewById(R.id.mb_item_option);
                setToolPanelVisibility(GONE);
                if (mEnabled) {
                    itemView.setOnClickListener((v) -> setToolPanelVisibility(GONE));
                    itemView.setOnLongClickListener((v) -> {
                        setToolPanelVisibility(VISIBLE);
                        return true;
                    });
                    btnItemDelete.setOnClickListener((v) -> removeMediaData(getBindingAdapterPosition()));
                }
            }
        }

        void setToolPanelVisibility(int visibility) {
            if (ivItemBorder != null && btnItemOption != null && btnItemDelete != null) {
                ivItemBorder.setVisibility(visibility);
                btnItemOption.setVisibility(visibility);
                btnItemDelete.setVisibility(visibility);
            }
        }

        void onResume() {}

        void onPause() {}

        void onDestroy() {}
    }

    private class TextViewHolder extends ViewHolder {

        EditText etText;

        public TextViewHolder(@NonNull View itemView) {
            super(itemView);
            etText = itemView.findViewById(R.id.et_text);
            etText.setEnabled(mEnabled);
        }

        public void setText(String text, int pos) {
            if (pos == 0 && mEnabled && mMediaDataList.size() == 1) {
                etText.setHint(R.string.et_content_placeholder);
            } else {
                etText.setHint(R.string.empty);
            }
            etText.setText(text);
            etText.setMinWidth(getWidth()); // 至少占据 1 行，便于选中
        }
    }

    private class ImageViewHolder extends ViewHolder {

        ImageView ivMain;

        public ImageViewHolder(@NonNull View itemView) {
            super(itemView);
            ivMain = itemView.findViewById(R.id.item_main_view);
        }

        public void loadImage(String url) {
            MarginLayoutParams layoutParams = (MarginLayoutParams) ivMain.getLayoutParams();
            int imageWidth = getWidth() - layoutParams.leftMargin - layoutParams.rightMargin;
            DisplayUtil.setViewSize(ivMain, imageWidth - 1, (int) (mImagePlaceholderAspectRatio * imageWidth)); // 设置“加载中”图片高度
            // 手动调节加载后的图片大小。自动调节会出错。
            ImageLoader.getInstance().displayImage(url, ivMain, DISPLAY_IMAGE_OPTIONS,
                    new SimpleImageLoadingListener() {
                @Override
                public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                    if (loadedImage == null)
                        return;
                    DisplayUtil.setViewSize(ivMain, imageWidth, LayoutParams.WRAP_CONTENT);
                    ivMain.setImageBitmap(ImageUtil.resize(loadedImage, imageWidth, ImageUtil.KEEP_ASPECT_RATIO));
                }
            });
        }
    }

    private class AudioViewHolder extends ViewHolder implements View.OnClickListener {

        static final int SEEKBAR_REFRESHING_TIME = 100; // milliseconds
        final String LOG_TAG_AUDIO = AudioViewHolder.class.getSimpleName(); // 这里不能设置为 static

        EditText etName;
        TextView tvTotalTime;
        TextView tvTime;
        SeekBar sbMusic;
        ImageView ivPlay;
        MediaPlayer mMediaPlayer;
        Uri uri;
        boolean mSeekBarChanging = false;
        Timer mTimer;
        SimpleDateFormat format = new SimpleDateFormat("mm:ss", Locale.getDefault());

        public AudioViewHolder(@NonNull View itemView) {
            super(itemView);
            etName = itemView.findViewById(R.id.et_audio_title);
            tvTotalTime = itemView.findViewById(R.id.tv_total_time);
            tvTime = itemView.findViewById(R.id.tv_time);
            sbMusic = itemView.findViewById(R.id.sb_music);
            ivPlay = itemView.findViewById(R.id.iv_play);

            DisplayUtil.setViewSize(itemView, getWidth(), LayoutParams.WRAP_CONTENT);
            etName.setEnabled(mEnabled);
            ivPlay.setOnClickListener(this);
            // 版权声明：本文为CSDN博主「tracydragonlxy」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。
            //  原文链接：https://blog.csdn.net/tracydragonlxy/article/details/77978630);
            sbMusic.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                    tvTime.setText(format.format(progress));
                }

                public void onStartTrackingTouch(SeekBar seekBar) {
                    mSeekBarChanging = true;
                }

                public void onStopTrackingTouch(SeekBar seekBar) {
                    mSeekBarChanging = false;
                    mMediaPlayer.seekTo(seekBar.getProgress());
                }
            });
        }

        public void loadAudio(String url, String name) {
            etName.setText(name);
            if (url.startsWith(FileUtil.UPLOADED_URL_PREFIX)) {
                try {
                    CompletableFuture.runAsync(() -> {
                        uri = FileUtil.downloadFile(url);
                        itemView.post(() -> onLoaded(name == null || name.isEmpty()));
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            else {
                uri = Uri.parse(url);
                onLoaded(name == null || name.isEmpty());
            }
        }

        public void onLoaded(boolean adoptingDefaultName) {
            if (uri == null) {
                Log.w(LOG_TAG_AUDIO, "generating uri failure");
                return;
            }
            if (adoptingDefaultName) {
                etName.setText(FileUtil.getUriColumn(uri, OpenableColumns.DISPLAY_NAME, null, null));
            }
            mMediaPlayer = MediaPlayer.create(getContext(), uri);
            if (mMediaPlayer == null) {
                Log.e(LOG_TAG_AUDIO, "create MediaPlayer failed: uri: " + uri.toString());
                return;
            }
            mMediaPlayer.setOnPreparedListener(mp -> {
                sbMusic.setMax(mp.getDuration());
                tvTotalTime.setText(format.format(mp.getDuration()));
            });
            mMediaPlayer.setOnCompletionListener(mp -> {
                mMediaPlayer.seekTo(0);
                ivPlay.setImageResource(R.drawable.richtext_audio_play);
            });
            mMediaPlayer.setLooping(false);
        }

        @Override
        public void onClick(View v) {
            int id = v.getId();

            if (id == R.id.iv_play) {
                if (!mMediaPlayer.isPlaying()) {
                    ivPlay.setImageResource(R.drawable.richtext_audio_pause);
                    mMediaPlayer.start();//开始播放
                    mMediaPlayer.seekTo(sbMusic.getProgress());
                    mTimer = new Timer();
                    mTimer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            if (!mSeekBarChanging) {
                                int pos = mMediaPlayer.getCurrentPosition();
                                sbMusic.setProgress(pos);
                                itemView.postDelayed(() -> tvTime.setText(format.format(pos)), 0);
                            }
                        }
                    }, 0, SEEKBAR_REFRESHING_TIME);
                } else {
                    ivPlay.setImageResource(R.drawable.richtext_audio_play);
                    mMediaPlayer.pause();
                    mTimer.cancel();
                }
            }
        }

        @Override
        public void onPause() {
            if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
                mMediaPlayer.pause();
            }
            if (mTimer != null) {
                mTimer.cancel();
            }
            Log.d(LOG_TAG_AUDIO, "media player paused when view on pause");
            ivPlay.setImageResource(R.drawable.richtext_audio_play);
        }

        @Override
        public void onDestroy() {
            if (mMediaPlayer != null) {
                mMediaPlayer.stop();
                mMediaPlayer.release();
            }
        }
    }

    // TODO(Low): 全屏后，视频名称不能立刻显示
    // TODO(Low): bug(在模拟器上)：录制的视频，总时间不准确
    private class VideoViewHolder extends ViewHolder {

        static final String TAG = "GSYVideoPlayerInVideoViewHolder";
        static final int MAX_VIDEO_HEIGHT = 230; // dp

        CoverVideoView gsyVideoPlayer;

        public VideoViewHolder(@NonNull View itemView) {
            super(itemView);
            gsyVideoPlayer = itemView.findViewById(R.id.cvv_video);
            if (mEnabled) {
                gsyVideoPlayer.getTitleTextView().setHint(R.string.item_file_name_placeholder);
            }
            gsyVideoPlayer.getTitleTextView().setEnabled(mEnabled);
            gsyVideoPlayer.loadCoverImage(R.drawable.image_black);
            gsyVideoPlayer.getTitleTextView().setVisibility(View.GONE);
            gsyVideoPlayer.setKeepingShowingTitle(true); // 加载时不显示标题，加载后如果是编辑状态，持续显示标题
            gsyVideoPlayer.getBackButton().setVisibility(View.GONE); // 不设置返回键
            gsyVideoPlayer.getFullscreenButton().setOnClickListener(v ->
                    gsyVideoPlayer.startWindowFullscreen(getContext(), false, true)); // 设置全屏按键功能
            gsyVideoPlayer.setPlayTag(TAG); // 防止错位设置
            gsyVideoPlayer.setReleaseWhenLossAudio(false); // 音频焦点冲突时是否释放
            gsyVideoPlayer.setShowFullAnimation(true); // 全屏动画
            gsyVideoPlayer.setIsTouchWiget(false); // 小屏时不触摸滑动
            gsyVideoPlayer.enableFullScreenManually(!mEnabled); // 可编辑状态下不可全屏
            if (mEnabled) {
                gsyVideoPlayer.setMyOnClickListener((v) -> setToolPanelVisibility(GONE));
                gsyVideoPlayer.setMyOnLongClickListener((v) -> {
                    setToolPanelVisibility(VISIBLE);
                    return true;
                });
            }
            gsyVideoPlayer.getCoverImageView().setMaxHeight(DisplayUtil.dp2px(MAX_VIDEO_HEIGHT));
        }

        public void loadVideo(String url, String coverUrl, int position, String name) {
            if ((name == null || name.isEmpty()) && !url.startsWith(FileUtil.UPLOADED_URL_PREFIX)) {
                // 获取默认名称
                name = FileUtil.getUriColumn(Uri.parse(url), OpenableColumns.DISPLAY_NAME, null, null);
            }
            gsyVideoPlayer.getTitleTextView().setText(name);
            if ((name != null && !name.isEmpty()) || mEnabled) {
                gsyVideoPlayer.getTitleTextView().setVisibility(View.VISIBLE);
            }
            if (coverUrl == null || coverUrl.isEmpty() || mEnabled) {
                Log.d(LOG_TAG, "generate video cover");
                gsyVideoPlayer.loadCoverImage(url, R.drawable.image_black);
            } else {
                Log.d(LOG_TAG, "begin loading video cover: " + coverUrl);
                gsyVideoPlayer.getStartCPView().setVisibility(View.INVISIBLE);
                ImageLoader.getInstance().displayImage(coverUrl, gsyVideoPlayer.getCoverImageView(), DISPLAY_VIDEO_COVER_OPTIONS,
                        new SimpleImageLoadingListener() {
                            @Override
                            public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                                if (loadedImage == null)
                                    return;
                                gsyVideoPlayer.setCoverBitmap(loadedImage);
                                gsyVideoPlayer.getStartCPView().setVisibility(View.VISIBLE);
                            }
                            @Override
                            public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
                                BaseUtil.showToast(R.string.toast_load_video_cover_failure);
                                gsyVideoPlayer.getStartCPView().setVisibility(View.VISIBLE);
                            }
                        });
            }
            gsyVideoPlayer.setUpLazy(url, true, null, null, name == null ? "" : name);
            gsyVideoPlayer.setPlayPosition(position);
        }

        public String getName() {
            return gsyVideoPlayer.getTitleTextView().getText().toString();
        }

        public String getGeneratedUri() {
            return gsyVideoPlayer.getGeneratedUri();
        }
    }

    private class Adapter extends RecyclerView.Adapter<ViewHolder> {

        @Override
        public int getItemViewType(int position) {
            return mMediaDataList.get(position).getType();
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            switch (viewType) {
                case MediaData.TYPE_IMAGE:
                    View imageView = View.inflate(parent.getContext(), R.layout.view_item_richtext_image, null);
                    return new ImageViewHolder(imageView);

                case MediaData.TYPE_AUDIO:
                    View audioView = View.inflate(parent.getContext(), R.layout.view_item_richtext_audio, null);
                    return new AudioViewHolder(audioView);

                case MediaData.TYPE_VIDEO:
                    View videoView = View.inflate(parent.getContext(), R.layout.view_item_richtext_video, null);
                    return new VideoViewHolder(videoView);

                default: // 默认展示文本
                    View textView = View.inflate(parent.getContext(), R.layout.view_item_richtext_text, null);
                    return new TextViewHolder(textView);
            }
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

            switch (getItemViewType(position)) {
                case MediaData.TYPE_TEXT:
                    ((TextViewHolder) holder).setText(mMediaDataList.get(position).getText(), position);
                    break;
                case MediaData.TYPE_IMAGE:
                    ((ImageViewHolder) holder).loadImage(mMediaDataList.get(position).getUri());
                    break;
                case MediaData.TYPE_AUDIO:
                    ((AudioViewHolder) holder).loadAudio(mMediaDataList.get(position).getUri(),
                            mMediaDataList.get(position).getText());
                    break;
                case MediaData.TYPE_VIDEO:
                    MediaData mediaData = mMediaDataList.get(position);
                    ((VideoViewHolder) holder).loadVideo(mediaData.getUri(),
                            mediaData.fetchJsonParams().getString(MediaData.PARAM_COVER_URL),
                            position, mediaData.getText());
                    break;
            }
        }

        @Override
        public int getItemCount() {
            return mMediaDataList.size();
        }
    }

    static final String LOG_TAG = RichTextView.class.getSimpleName();
    // 假设没有人可以在 DirtyTime 内重复进行添加多媒体，删除，上传操作。
    // 留出一段时间，保证 mAdapter 刷新成功，这段时间内，数据不会依据 View 刷新。
    static final int DIRTY_TIME = 500; // milliseconds
    static final int UPLOAD_BLOCK_WAITING_TIME = 200; // milliseconds
    static final int UPLOAD_BLOCK_MAX_SIZE = 5;
    static final int ABSTRACT_MAX_LENGTH = 100;

    static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS = new DisplayImageOptions.Builder()
            .showImageOnLoading(R.drawable.image_loading)
            .showImageForEmptyUri(R.drawable.image_not_found)
            .showImageOnFail(R.drawable.image_error)
            .cacheInMemory(true)
            .cacheOnDisk(true)
            .build();

    static final DisplayImageOptions DISPLAY_VIDEO_COVER_OPTIONS = new DisplayImageOptions.Builder()
            .showImageOnLoading(R.drawable.video_cover_loading)
            .showImageForEmptyUri(R.drawable.video_cover_lost)
            .showImageOnFail(R.drawable.video_cover_error)
            .cacheInMemory(true)
            .cacheOnDisk(true)
            .build();

    boolean mEnabled;
    List<MediaData> mMediaDataList = null;
    boolean mMediaDataDirty;
    boolean mInFullScreen = false;
    Adapter mAdapter;
    LinearLayoutManager mLayoutManager;
    OnClickListener mMyOnClickListener = null;
    float mImagePlaceholderAspectRatio = 0;

    public RichTextView(@NonNull Context context) {
        super(context);
    }

    public RichTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RichTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void init(String str, boolean enabled) {
        if (mImagePlaceholderAspectRatio == 0) {
            Bitmap loadingBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image_loading);
            mImagePlaceholderAspectRatio = (float) loadingBitmap.getHeight() / loadingBitmap.getWidth();
        }
        try {
            mMediaDataList = JSON.parseArray(str, MediaData.class);
        } catch (Exception ignored) {
            BaseUtil.showToast(R.string.toast_media_format_error);
            Log.w(LOG_TAG, "format error when initializing data");
        }
        if (mMediaDataList == null)
            mMediaDataList = new ArrayList<>();
        if (mMediaDataList.size() == 0) {
            mMediaDataList.add(new MediaData());
        }
        if (!enabled) { // (在非编辑模式下) 预处理，去除空行
            Iterator<MediaData> iterator = mMediaDataList.iterator();
            while (iterator.hasNext()) {
                MediaData mediaData = iterator.next();
                if (mediaData.getType() == MediaData.TYPE_TEXT) {
                    String text = mediaData.getText();
                    if (text == null || text.isEmpty()) {
                        iterator.remove();
                    } else if (text.endsWith("\n")) {
                        mediaData.setText(text.substring(0, text.length() - 1));
                    }
                }
            }
        }
        mMediaDataDirty = true;
        mEnabled = enabled;
        mAdapter = new Adapter();
        setAdapter(mAdapter);
        mLayoutManager = new LinearLayoutManager(getContext());
        setLayoutManager(mLayoutManager);
        // TODO(Low): 这里的逻辑待测试
        addOnScrollListener(new RecyclerView.OnScrollListener() { // 释放当前不可见的视频

            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                Log.d(LOG_TAG, "scroll: x: " + dx + ",y: " + dy);
                int firstVisibleItem, lastVisibleItem;
                if (GSYVideoManager.instance().getPlayPosition() >= 0) { // 如果正在播放
                    //当前播放的位置
                    int position = GSYVideoManager.instance().getPlayPosition();
                    firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
                    lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
                    //对应的播放列表TAG
                    if (GSYVideoManager.instance().getPlayTag().equals(VideoViewHolder.TAG)
                            && (position < firstVisibleItem || position > lastVisibleItem)) {
                        if(GSYVideoManager.isFullState((Activity)getContext())) {
                            return;
                        }
                        Log.d(LOG_TAG, "invisible video released, position: " + position);
                        GSYVideoManager.releaseAllVideos();
                        mAdapter.notifyItemChanged(position);
                    }
                }
            }
        });
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mInFullScreen != GSYVideoManager.isFullState((Activity)getContext())) {
            // 在onLayout里监视布局变化，全屏的时候隐藏 ActionBar
            mInFullScreen = GSYVideoManager.isFullState((Activity)getContext());
            AppCompatActivity activity = (AppCompatActivity) getContext();
            Log.d(LOG_TAG, "full screen state changed: " + mInFullScreen);
            if(mInFullScreen) {
                Objects.requireNonNull(activity.getSupportActionBar()).hide();
            } else {
                Objects.requireNonNull(activity.getSupportActionBar()).show();
            }
        }
    }

    public void onResume() {
        Log.d(LOG_TAG, "onResume");
        traverseMediaDataView(ViewHolder::onResume);
        GSYVideoManager.onResume();
    }

    // TODO(Mid): 考虑不同音频的相互作用，保证只有一个音频或者视频正在播放
    public void onPause() {
        Log.d(LOG_TAG, "onPause");
        traverseMediaDataView(ViewHolder::onPause);
        GSYVideoManager.onPause();
    }

    public void onDestroy() {
        Log.d(LOG_TAG, "onDestroy");
        traverseMediaDataView(ViewHolder::onDestroy);
        GSYVideoManager.releaseAllVideos();
    }

    public void traverseMediaDataView(Consumer<ViewHolder> callback) {
        // 注意，不能使用 getChildCount()，因为 getChildCount() 只会显示当前显示的 View 数量，而不是全部的 View
        for (int i = 0; i < mAdapter.getItemCount(); i++) {
            ViewHolder viewHolder = (ViewHolder) findViewHolderForAdapterPosition(i);
            if (viewHolder != null) {
                callback.accept(viewHolder);
            }
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        if (ev.getAction() == MotionEvent.ACTION_UP && mMyOnClickListener != null) {
            View view = getFocusedChild();
            if (view instanceof ConstraintLayout)
                view = ((ConstraintLayout) view).getFocusedChild();
            mMyOnClickListener.onClick(view == null ? this : view);
        }
        return super.dispatchTouchEvent(ev);
    }

    public void setMyOnClickListener(OnClickListener inOnClickListener) {
        this.mMyOnClickListener = inOnClickListener;
    }

    public String getContent() {
        Log.d(LOG_TAG, "content: " + JSON.toJSONString(getMediaDataList()));
        return JSON.toJSONString(getMediaDataList());
    }

    public String generateAbstract() {
        StringBuilder builder = new StringBuilder();
        List<MediaData> mediaDataList = getMediaDataList();

        for (int i = 0; i < mediaDataList.size(); i++) {
            MediaData data = mediaDataList.get(i);
            if (data.getType() != MediaData.TYPE_TEXT)
                continue;
            if (builder.length() > 0)
                builder.append('\n');
            builder.append(data.getText());
            if (builder.length() > ABSTRACT_MAX_LENGTH)
                return builder.subSequence(0, ABSTRACT_MAX_LENGTH).toString();
        }
        return builder.toString();
    }

    public boolean isEmpty() {
        List<MediaData> mediaDataList = getMediaDataList();
        for (int i = 0; i < mediaDataList.size(); i++) {
            MediaData data = mediaDataList.get(i);
            if (data.getType() != MediaData.TYPE_TEXT || !data.getText().isEmpty())
                return false;
        }
        return true;
    }

    public List<MediaData> getMediaDataList() {
        if (mMediaDataDirty) {
            Log.i(LOG_TAG, "refresh data");
            for (int i = 0; i < mAdapter.getItemCount(); i++) {
                switch (mAdapter.getItemViewType(i)) {
                    case MediaData.TYPE_TEXT:
                        EditText editText = getViewAt(i).findViewById(R.id.et_text);
                        mMediaDataList.get(i).setText(editText.getText().toString());
                        break;
                    case MediaData.TYPE_IMAGE:
                        break;
                    case MediaData.TYPE_VIDEO:
                        VideoViewHolder videoViewHolder = (VideoViewHolder) findViewHolderForAdapterPosition(i);
                        if (videoViewHolder == null) {
                            Log.d(LOG_TAG, "videoViewHolder not found");
                            break;
                        }
                        mMediaDataList.get(i).setText(videoViewHolder.getName());
                        mMediaDataList.get(i).putJsonParam(MediaData.PARAM_COVER_URL, videoViewHolder.getGeneratedUri());
                        break;
                    case MediaData.TYPE_AUDIO:
                        EditText etAudioTitle = getViewAt(i).findViewById(R.id.et_audio_title);
                        mMediaDataList.get(i).setText(etAudioTitle.getText().toString());
                        break;
                    default:
                        Log.w(LOG_TAG, "media type error");
                        new Exception().printStackTrace();
                }
            }
            mMediaDataDirty = false;
            this.postDelayed(()-> mMediaDataDirty = true, DIRTY_TIME);
        }
        return mMediaDataList;
    }

    public int getStyle() {
        return getStyle(getMediaDataList());
    }

    public static int getStyle(List<MediaData> mMediaDataList) {
        int style = Post.STYLE_TEXT_ONLY;
        for (MediaData mediaData : mMediaDataList) {
            if (mediaData.getType() == MediaData.TYPE_IMAGE) {
                style = style & ~Post.STYLE_TEXT_ONLY;
                style = style | Post.STYLE_IMAGE;
            }
            if (mediaData.getType() == MediaData.TYPE_AUDIO) {
                style = style & ~Post.STYLE_TEXT_ONLY;
                style = style | Post.STYLE_AUDIO;
            }
            if (mediaData.getType() == MediaData.TYPE_VIDEO) {
                style = style & ~Post.STYLE_TEXT_ONLY;
                style = style | Post.STYLE_VIDEO;
            }
        }
        return style;
    }

    public void addMediaData(MediaData mediaData) {
        if (mediaData == null) {
            Log.i(LOG_TAG, "empty media, maybe fetching media was canceled by user");
            return;
        }
        Log.d(LOG_TAG, "find media: uri: " + mediaData.getUri() +
                ", path: " + FileUtil.getFilePath(Uri.parse(mediaData.getUri())));
        if (mediaData.getType() == MediaData.TYPE_TEXT) {
            Log.w(LOG_TAG, "add new text section manually is not allowed");
            return;
        }
        int focusedIndex = getFocusedPosition();
        if (focusedIndex == -1) {
            focusedIndex = mAdapter.getItemCount() - 1;
        }
        EditText focusedEditText = getViewAt(focusedIndex).findViewById(R.id.et_text);
        if (focusedEditText == null) {
            Log.e(LOG_TAG, "find edit text failure");
            return;
        }
        String text = focusedEditText.getText().toString();
        int cursorIndex = focusedEditText.getSelectionStart();
        String firstPart = text.substring(0, cursorIndex);
        String secondPart = text.substring(cursorIndex);
        int mediaIndex = focusedIndex + 1; // 待插入的媒体位置
        Log.d(LOG_TAG, "index:" + mediaIndex + ", new media:" + JSON.toJSONString(mediaData) +
                "\nfirstPart: " + firstPart + "\nsecondPart: " + secondPart);
        if (secondPart.startsWith("\n")) {
            secondPart = secondPart.substring(1);
            Log.d(LOG_TAG, "remove empty line");
        } else if (firstPart.endsWith("\n")) { // 只移除至多 1 行，优先从第二部分移除
            firstPart = firstPart.substring(0, firstPart.length() - 1);
            Log.d(LOG_TAG, "remove empty line");
        }
        mMediaDataList.get(focusedIndex).setText(firstPart);
        // 文字与多媒体严格交替排列。在展示的时候，隐藏空白的文本
        mMediaDataList.add(mediaIndex, new MediaData(MediaData.TYPE_TEXT, "", secondPart, ""));
        // 焦点落在第二段文字上
        postDelayed(() -> requestInnerFocus(mediaIndex + 1), DisplayUtil.REFRESHING_PAGE_WAITING_TIME);
        mMediaDataList.add(mediaIndex, mediaData);
        mAdapter.notifyItemChanged(focusedIndex);
        mAdapter.notifyItemRangeInserted(mediaIndex, 2);
        mMediaDataDirty = false;
        this.postDelayed(()-> mMediaDataDirty = true, DIRTY_TIME);
        getContent();
    }

    public void removeMediaData(int pos) {
        mMediaDataList.remove(pos);
        ViewHolder mediaHolder = (ViewHolder) findViewHolderForAdapterPosition(pos);
        if (mediaHolder != null) {
            mediaHolder.onPause();
            mediaHolder.onDestroy();
        }
        mMediaDataDirty = false;
        if ((pos > 0 && mMediaDataList.get(pos - 1).getType() == MediaData.TYPE_TEXT) &&
                (pos < mMediaDataList.size() && mMediaDataList.get(pos).getType() == MediaData.TYPE_TEXT)) {
            String firstPart = ((EditText) getViewAt(pos - 1).findViewById(R.id.et_text)).getText().toString();
            // 注意这里 model 更新了，但是 view 没有更新
            String secondPart = ((EditText) getViewAt(pos + 1).findViewById(R.id.et_text)).getText().toString();
            mMediaDataList.get(pos - 1).setText(firstPart + "\n" + secondPart);
            mMediaDataList.remove(pos);
            Log.d(LOG_TAG, "two parts of text merged");
        } else {
            Log.w(LOG_TAG, "media data was not surround by two parts of text");
            return;
        }
        mAdapter.notifyItemChanged(pos - 1);
        mAdapter.notifyItemRangeRemoved(pos, 2);
        this.postDelayed(()-> mMediaDataDirty = true, DIRTY_TIME);
        getContent();
    }

    public View getViewAt(int i) {
        return mLayoutManager.findViewByPosition(i);
    }

    public int getFocusedPosition() {
        for (int i = 0; i < mAdapter.getItemCount(); i++) {
            if (getViewAt(i) != null && getViewAt(i).hasFocus()) {
                return i;
            }
        }
        return -1;
    }

    public View requestInnerFocus() {
        return requestInnerFocus(mAdapter.getItemCount() - 1);
    }

    public View requestInnerFocus(int index) {
        EditText editText = null;
        try {
            editText = getViewAt(index).findViewById(R.id.et_text);
        } catch (Exception ignored) { }
        if (editText != null) {
            editText.requestFocus();
        } else {
            new Exception().printStackTrace();
            Log.w(LOG_TAG, "requestInnerFocus failed at index: " + index);
        }
        return editText;
    }

    // TODO(Low): 上传逻辑抽出来一个 Service，显示上传进度
    public void checkAndUpload(Consumer<Boolean> callback) {
        Counter<Boolean> counter = new Counter<>(1, (bResult) -> {
            callback.accept(bResult);
            // 由于上传图片时在刷新数据，所以数据不应该刷新。数据处理结束后，数据置脏，刷新。
            this.postDelayed(()-> mMediaDataDirty = true, DIRTY_TIME);
        });
        counter.setRecord(true);

        getContent(); // 先刷新资源

        loop: for (int i = 0; i < mMediaDataList.size(); i++) {

            MediaData mediaData = mMediaDataList.get(i);
            if (mediaData.getUri().startsWith(FileUtil.UPLOADED_URL_PREFIX))
                continue;
            switch (mediaData.getType()) {
                case MediaData.TYPE_TEXT:
                    continue loop;
                case MediaData.TYPE_VIDEO:
                    try {
                        while (counter.count() > UPLOAD_BLOCK_MAX_SIZE)
                            //noinspection BusyWait
                            Thread.sleep(UPLOAD_BLOCK_WAITING_TIME);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String coverUrl = mediaData.fetchJsonParams().getString(MediaData.PARAM_COVER_URL);
                    FileUtil.uploadMediaData(coverUrl, MediaData.TYPE_IMAGE, (bResult, newUrl) -> {
                        if (bResult) {
                            synchronized (mediaData) {
                                mediaData.putJsonParam(MediaData.PARAM_COVER_URL, newUrl);
                                String newMediaString = JSON.toJSONString(mediaData);
                                Log.d(LOG_TAG, "cover image uploaded: " + newMediaString);
                                mMediaDataDirty = false;
                            }
                        } else {
                            counter.setRecord(false);
                        }
                        counter.countDown();
                    });
                case MediaData.TYPE_IMAGE: // 注意上方没有 break;
                case MediaData.TYPE_AUDIO:
                    try {
                        while (counter.count() > UPLOAD_BLOCK_MAX_SIZE)
                            //noinspection BusyWait
                            Thread.sleep(UPLOAD_BLOCK_WAITING_TIME);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    FileUtil.uploadMediaData(mediaData.getUri(), mediaData.getType(), (bResult, newUrl) -> {
                        if (bResult) {
                            synchronized (mediaData) {
                                mediaData.setUri(newUrl);
                                String newMediaString = JSON.toJSONString(mediaData);
                                Log.d(LOG_TAG, "uploaded media: " + newMediaString);
                                mMediaDataDirty = false;
                            }
                        } else {
                            counter.setRecord(false);
                        }
                        counter.countDown();
                    });
            }
        }
        counter.countDown(); // 额外计数一次，如果没有图片，这里会直接回调；如果有图片，等图片上传完成后回调
    }
}
